package com.bigflexlib.utils
{
	
	import com.bigflexlib.x.mx.core.Application.App;
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.utils.getTimer;
	


/**
 *  @eventType flash.events.Event.COMPLETE
 */
[Event(name="complete", type="flash.events.Event")]	
	
	




	/**
	 * 
	 * Implements a simple Async Execution pattern to break resource-intensive tasks
	 * ( like large document parsing ) in the absence of threads.
	 * 
	 * @author aldo.bucchi@gmail.com
	 * 
	 */
	public class AsyncRunner extends EventDispatcher
	{
		
		
		
		
		// this is stored here instead of calculated on-demand for performance reasons
		private var _isComplete:Boolean = true;
		[Bindable("complete")]
		public function get isComplete( ):Boolean
		{
			return _isComplete;		
		}



		/**
		 * 
		 * The stack is an array of queues.
		 * 
		 */
		private var stack:Array = [ [] ];
		


		/**
		 * 
		 * The current queue is located at the end of the stack
		 * 
		 * @return 
		 * 
		 */
		private function get currentQueue():Array
		{
			if ( stack.length == 0 )
				return null;
				
			return stack[ stack.length - 1 ] as Array;
		}




		/**
		 * 
		 * You don't instantiate an AsyncRunner directly.
		 * Instead, you call AsyncRunner.create( ) to create a new runner thread.
		 * You can then call runner.step() to execute each step manually
		 * 
		 */
		public function AsyncRunner( )
		{

		
		}





		/**
		 * 
		 * @param func
		 * @param args
		 * 
		 */		 
		private function queue( func:Function, args:Array = null ):void
		{
			currentQueue.push( [ func, args ] );
			_isComplete = false;
		}

		
		
		
		/**
		 * 
		 * consumes one step from the queue
		 * 
		 */		
		public function step( ):void
		{
			if ( _isComplete )
			{
				return;			
			}



			while ( currentQueue.length == 0 )
			{
				// the current queue has no more entries left, we can pop it and move down the stack
				stack.pop( );
				if ( currentQueue == null )	
				{
					// the stack is exhausted, we're done
					_isComplete = true;
					dispatchEvent( new Event( Event.COMPLETE ) );
					App.app.removeEventListener( Event.ENTER_FRAME, app_enterFrame );
					return;
				}		
			}
			
			
			var entry:Array = currentQueue.shift() as Array;		
			// append to the stack so any calls made when executing this entry
			// are stored in their own queue
			stack.push( [] );
			
			
			
			//////////////// execute //////////////////
			
			
			var func:Function 	= entry[ 0 ] as Function;
			
			var args:Array = [];
			
			if ( entry[1] && entry[1] is Array )
				args = ( entry[1] as Array );


			runnerStack.push( this );
			func.apply( null, args );
			runnerStack.pop( );
		}
		
		
		
		
		
		
		
		private var endTime:int;
		/**
		 * 
		 * Will execute this thread for ( a bit longer than ) the amount 
		 * of time specified.
		 * 
		 * if no argument is passed it will run to completion
		 * 
		 * @param elapsed the time, in milliseconds, that this thread is given to run. defaults to -1 ( no limit )
		 * 
		 */		
		public function run( elapsed:int = -1 ):void
		{
			endTime = getTimer() + elapsed;
			while (  ( elapsed == -1 ) || ( getTimer( ) < endTime ) )
			{
				
				step( );
				
				if ( _isComplete )
					break;
					
			}
		
		}
		
		
		
		
		/**
		 * 
		 * A common use case.
		 * Will add an enter frame listener and try to run( elapsed ) on each frame.
		 * 
		 * @param elapsed
		 * 
		 */		
		public function runEachFrame( elapsed:int = 100 ):void
		{
			elapsedPerFrame = elapsed;
			App.app.addEventListener( Event.ENTER_FRAME, app_enterFrame );			
		}
		
		private var elapsedPerFrame:int;
		
		private function app_enterFrame( event:Event ):void
		{
			run( elapsedPerFrame );
		}		

		
		

		
		
		
		////////////////////////////////////////////////////////////////////
		///////////////////////////// static ///////////////////////////////
		////////////////////////////////////////////////////////////////////
		


		/**
		 * 
		 * By using a stack here we allow for nested async runners 
		 * ( a runner generated within another runner )
		 * 
		 * We are still missing some logic to chain runners, but
		 * there are no use cases yet so we don't really care
		 * 
		 */
		private static var runnerStack:Array = [];

		
		
		
		
		/**
		 * 
		 * 
		 * Creates a runner instance, starting at the given function.
		 * You can then call runner.step() to execute the first step and any
		 * other nested steps that get queued as well.
		 * 
		 * 
		 * @param func
		 * @param args
		 * @return 
		 * 
		 */		
		public static function create( func:Function, args:Array = null ):AsyncRunner
		{
			var runner:AsyncRunner = new AsyncRunner( );
			runner.queue( func, args );
			return runner;
		}
		
		
		
		
		/**
		 * 
		 * Even though this function is static, it operates on the current active runner thread.
		 * It is only meant to be called from within functions that are designed to work with async
		 * threads.
		 * 
		 * @param func
		 * @param args
		 * 
		 */		
		public static function queue( func:Function, args:Array = null ):void
		{
			if ( runnerStack.length > 0 )
			{
				// append call to the current ( topmost ) runner's queue
				( runnerStack[ runnerStack.length - 1 ] as AsyncRunner ).queue( func, args );		
			}
			else
			{
				throw new Error( "There is no active AsyncRunner. You cannot queue a method call." );	
			}
		}
		
	}

}